Skip to content

geotiff: apply TIFF Orientation tag on HTTP COG full reads (#1717)#1724

Merged
brendancol merged 1 commit into
mainfrom
issue-1717
May 12, 2026
Merged

geotiff: apply TIFF Orientation tag on HTTP COG full reads (#1717)#1724
brendancol merged 1 commit into
mainfrom
issue-1717

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

_read_cog_http's full-read branch returned the raw decoded buffer without running _apply_orientation, so the same file came back with a different pixel order over HTTP than locally. This pulls the orientation + geo_info update into a helper that both paths call.

Repro

import tifffile, numpy as np
from xrspatial.geotiff._reader import read_to_array, _read_cog_http

arr = np.arange(48, dtype=np.uint8).reshape(6, 8)
tifffile.imwrite("oriented.tif", arr, extratags=[(274, 'H', 1, 4, True)])

local, _ = read_to_array("oriented.tif")
http, _ = _read_cog_http("http://.../oriented.tif")
assert np.array_equal(local, http)  # fails before this PR

Fix

New _apply_orientation_with_geo(arr, geo_info, orientation) in _reader.py; read_to_array and _read_cog_http both call it. The windowed-read + non-default-orientation rejection is unchanged on both paths.

Tests

xrspatial/geotiff/tests/test_http_orientation_1717.py covers orientations 1-8 via the same in-memory loopback server pattern as test_cog_http_concurrent.py. Full local vs HTTP reads match (array and transform), windowed HTTP reads against non-default orientation still raise ValueError.

16 new tests pass. test_cog_http_concurrent.py, test_orientation.py, test_orientation_gpu.py, and the other test_http_* suites still pass.

Closes #1717

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label May 12, 2026
@brendancol brendancol requested a review from Copilot May 12, 2026 19:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a backend-parity bug in the GeoTIFF reader where full HTTP COG reads (_read_cog_http) returned the decoded pixel buffer without applying the TIFF Orientation tag (274), causing different pixel order (and potentially different geospatial transform interpretation) compared to local reads.

Changes:

  • Apply the Orientation tag handling to the HTTP full-read path so HTTP and local reads return consistent arrays and GeoInfo.
  • Factor shared orientation + GeoInfo.transform update logic into a new _apply_orientation_with_geo(...) helper.
  • Add a new HTTP parity test suite covering orientations 1–8 and ensuring windowed HTTP reads still reject non-default orientation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
xrspatial/geotiff/_reader.py Adds a shared orientation+georef helper and uses it in both local and HTTP read paths.
xrspatial/geotiff/tests/test_http_orientation_1717.py New regression tests to ensure HTTP full reads match local reads for all orientation values and preserve the windowed-read rejection behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +99 to +120
arr_local, geo_local = read_to_array(str(path))

httpd, port = _serve(payload)
try:
url = f'http://127.0.0.1:{port}/orient_{orientation}.tif'
arr_http, geo_http = _read_cog_http(url)
finally:
httpd.shutdown()
httpd.server_close()

assert arr_http.shape == arr_local.shape, (
f"orientation={orientation}: HTTP shape {arr_http.shape} != "
f"local shape {arr_local.shape}"
)
np.testing.assert_array_equal(
arr_http, arr_local,
err_msg=f"orientation={orientation}: HTTP pixels differ from local",
)
assert geo_http.transform == geo_local.transform, (
f"orientation={orientation}: transform mismatch "
f"http={geo_http.transform} local={geo_local.transform}"
)
The HTTP path in `_read_cog_http` returned the raw decoded buffer for
full reads, skipping the `_apply_orientation` remap that the local path
runs in `read_to_array`. Opening the same file locally vs over HTTP
produced different pixel orders and transforms for any Orientation tag
value other than 1.

Extract the orientation + geo_info update into
`_apply_orientation_with_geo` and call it from both paths. The existing
rejection of windowed reads against non-default orientation is kept
unchanged on both paths.

Tests cover orientations 1-8 via local + HTTP round-trip against the
same in-memory loopback server used by `test_cog_http_concurrent.py`,
plus a regression check that windowed HTTP reads still raise on
non-default orientation.
@brendancol brendancol merged commit 75324c1 into main May 12, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

geotiff: HTTP COG full reads skip TIFF Orientation tag

2 participants